# -*- coding: utf-8 -*-
#===================================================================================================
# Created on 23 Mar 2011
# 
# @author: serj
#===================================================================================================

import re
import logging
import simplejson
import atom
import urllib2
import traceback
import gdata.alt.appengine
import gdata.client
import time
from hashlib import sha1
from gdata.service import RequestError
from google.appengine.api import users
from google.appengine.api import memcache
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from service import models
from service.html import WaveletParser
from service.html import GDocsBlipParser
from waveapi import waveservice
from waveapi import ops
from waveapi import errors
from service.settings import WAVE_CONSUMER_KEY, WAVE_CONSUMER_SECRET
from service.settings import WAVE_PREVIEW, WAVE_SANDBOX
from service.settings import SELF_DOMAIN

_CLOSE_PAGE = """
<html>
  <head>
    <script type="text/javascript">
    function closeMe(){
        window.opener.popupWnd.close();
    } 
    </script>
  </head>
  <body onload="closeMe()"></body>
</html>
"""

class ServiceError(Exception):
    pass

class ServiceHandler(webapp.RequestHandler):
    '''
    An interface for publish services
    '''
    def __init__(self):
        self.actions = {}
        self.actions['getAccessToken'] = (self.getAccessToken, 'text/javascript')

    def getAccessToken(self):
        user = users.get_current_user()
        if not user: return;
#        user_id = self.request.get('userId')
#        wave_id = self.request.get('waveId')
#        if not user_id or not wave_id: return
        memcache_client = memcache.Client()
        session = memcache_client.get(self.settings['MEMCACHE_USER_STORE'] % user.email())
        try:
            access_token = session['access_token']
        except:
            logging.warn(traceback.format_exc())
            return
        if not access_token: return
        if hasattr(access_token, 'get_token_string'):
            return simplejson.dumps({'token': access_token.get_token_string()})
        if hasattr(access_token, 'token_secret') and hasattr(access_token, 'token'):
            return simplejson.dumps({'token': 'oauth_token_secret=%s&oauth_token=%s' %
                                     (access_token.token_secret,
                                      access_token.token)})
        return simplejson.dumps({'token': access_token})

    def generateHtml(self):
        raise NotImplementedError

    def get(self, action):
        self.locale = self.request.get('locale')
        (handler, mime_type) = self.actions.get(action, (None, None))
        if handler:
            if mime_type:
                self.response.headers['Cache-Control'] = 'private, no-cache'
                self.response.headers['Expires'] = 'Wed, 17 Sep 1975 21:32:10 GMT'
                self.response.headers['Content-Type'] = mime_type + '; charset=utf-8'
            content = handler()
            if content:
                logging.info(content)
                self.response.out.write(content)


    def post(self):
        logging.info('post request')

    def requestRemoteData(self, url, error_class=Exception, data=None):
        # create request to remote server and process response
        try:
            # processing response
            f = urllib2.urlopen(url, data)
            if f.code == 200:
                data = f.read()
            else:
                logging.warn('Non 200 response: ' + f.code)
                data = f.read()
            logging.info('Received Remote Data:')
            logging.info(data)
            return data
        except urllib2.HTTPError, e:
            # processing error
            logging.warn(traceback.format_exc())
            raise error_class, {'status': e.code, 'body': e.read()}

    def getErrorResponse(self, error):
        ''' parsing error exceptions '''
        logging.warn(error)
        logging.warn(traceback.format_exc())
        if type(error) == ServiceError:
            error = error.args[0]
            self.response.set_status(error['status'])
            return simplejson.dumps({'data': error['body']})
        if type(error) == RequestError or isinstance(error, gdata.client.Error):
            if type(error) == RequestError:
                error = error.args[0]
            elif isinstance(error, gdata.client.Error):
                error = error.__dict__
            logging.info(error)
            status = error['status']
            self.response.set_status(status)
            if status == 401:
                return simplejson.dumps({'data': 'Token is invalid'})
            if status == 400:
                return simplejson.dumps({'data': 'Invalid blog id specified'})
            if status == 404:
                return simplejson.dumps({'data': 'Post not found'})
            if status == 503:
                return simplejson.dumps({'data': 'Blogger service currently unavailable'})
            return simplejson.dumps({'data': error['body']})
        if type(error) == VolnaSiteError:
            error = error.args[0]
            logging.info(error)
            status = error['status']
            self.response.set_status(status)
            if status == 401:
                return simplejson.dumps({'data': 'Token is invalid'})
            if status == 400:
                return simplejson.dumps({'data': 'Invalid menu id specified'})
            if status == 404:
                return simplejson.dumps({'data': 'Document not found'})
            return simplejson.dumps({'data': error['body']})
        if type(error) == errors.RpcError:
            error = error.args[0]
            error_list = error.split(':')
            status = int(error_list[0])
            self.response.set_status(status)
            return simplejson.dumps({'data': error_list[2]})
    
    def getRobotInstance(self, wave_id='!'):
        import robot
        bloggerbot = robot.getRobotInstance()

        rpc = WAVE_PREVIEW
        if wave_id.split('!')[0] == 'wavesandbox.com':
            rpc = WAVE_SANDBOX
        bloggerbot.setup_oauth(WAVE_CONSUMER_KEY, WAVE_CONSUMER_SECRET, rpc);
        return bloggerbot
    
    def getWaveletWithCopy(self, robot, wave_id, wavelet_id):
        wavelet = robot.fetch_wavelet(wave_id, wavelet_id)
        wavelet_copy = waveservice.WaveService()._wavelet_from_json(wavelet.serialize(), ops.OperationQueue())
        return wavelet, wavelet_copy


class GoogleService(ServiceHandler):
    def __init__(self, settings=None, service=None):
        super(GoogleService, self).__init__()
        self.settings = settings
        self.service = service(source=settings['APP_NAME'])
        if not isinstance(self.service, gdata.client.GDClient):
            self.service.SetOAuthInputParameters(settings['SIG_METHOD'], settings['CONSUMER_KEY'],
                                             consumer_secret=settings['CONSUMER_SECRET'])
            gdata.alt.appengine.run_on_appengine(self.service)
        self.actions['upgradeToken'] = (self.upgradeToken, 'text/html')
        self.actions['upgradeTokenV2'] = (self.upgradeTokenV2, 'text/html')
        self.actions['authCheck'] = (self.checkUserAuth, 'text/javascript')
        self.actions['deleteTokenV2'] = (self.deleteTokenV2, None)
        self.actions['logout'] = (self.logout, 'text/html')

    def deleteTokenV2(self):
        user = users.get_current_user()
        if not user: return
        gdata.gauth.AeDelete(self.settings['MEMCACHE_USER_STORE'] % user.email())

    def checkUserAuth(self):
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
            return
#        user_id = self.request.get('userId')
        wave_id = self.request.get('waveId')
#        if not user_id or not wave_id:
#            return
        if not isinstance(self.service, gdata.client.GDClient):
            method_name = 'upgradeToken'
        else:
            method_name = 'upgradeTokenV2'
        self.redirect(self.request.scheme + '://' + self.request.host +
                  '/service/%s/%s?waveId=%s&userId=%s' %
                        (self.settings['SERVICE_NAME'], method_name, wave_id, user.email()))

    def upgradeToken(self):
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
            return

        oauth_token = gdata.auth.OAuthTokenFromUrl(self.request.uri)
        memcache_client = memcache.Client()
        if oauth_token:
            try:
                session = memcache_client.get(self.settings['MEMCACHE_USER_STORE'] % user.email())
                oauth_token.secret = session['oauth_token_secret']
                oauth_token.oauth_input_params = self.service.GetOAuthInputParameters()
                self.service.SetOAuthToken(oauth_token)

                # Exchange the authorized request token for an access token
                oauth_verifier = self.request.get('oauth_verifier', default_value='')
                try:
                    access_token = self.service.UpgradeToOAuthAccessToken(oauth_verifier=oauth_verifier)
                except:
                    logging.warn(traceback.format_exc())
                    return _CLOSE_PAGE
                session['access_token'] = access_token
                memcache_client.set(self.settings['MEMCACHE_USER_STORE'] % user.email(), session, time=120)

                self.service.current_token = access_token
                self.service.SetOAuthToken(access_token)
                return _CLOSE_PAGE
            except:
                logging.warn(traceback.format_exc())
            finally:
                return _CLOSE_PAGE

        #===========================================================================================
        # REQUEST TOKEN STEP. Provide the data scope(s) and the page we'll
        # be redirected back to after the user grants access on the approval page.
        #===========================================================================================
        req_token = self.service.FetchOAuthRequestToken(scopes=self.settings['SCOPES'],
                oauth_callback=self.request.scheme + '://' + self.request.host +
                        self.request.path + '?userId=%s' % (user.email()))

        #===========================================================================================
        # When using HMAC, persist the token secret in order to re-create an
        # OAuthToken object coming back from the approval page.
        #===========================================================================================
        memcache_client.set(self.settings['MEMCACHE_USER_STORE'] % (user.email()),
                            {'oauth_token_secret': req_token.secret})

        #===========================================================================================
        # Generate the URL to redirect the user to.  Add the hd paramter for a
        # better user experience.  Leaving it off will give the user the choice
        # of what account (Google vs. Google Apps) to login with.
        #===========================================================================================
        domain = self.request.get('domain', default_value='default')
        approval_page_url = self.service.GenerateOAuthAuthorizationURL(
              extra_params={'hd': domain})

        # APPROVAL STEP.  Redirect to user to Google's OAuth approval page.
        self.redirect(approval_page_url)

    def upgradeTokenV2(self):
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
            return
        user_id = self.request.get('userId')
        if not user_id:
            return

        saved_request_token = gdata.gauth.AeLoad(self.settings['MEMCACHE_USER_STORE'] % user.email())
        logging.info(saved_request_token)
        if saved_request_token:
            try:
                gdata.gauth.AeDelete(self.settings['MEMCACHE_USER_STORE'] % user.email())
                oauth_token = gdata.gauth.AuthorizeRequestToken(saved_request_token, self.request.uri)
                # Exchange the authorized request token for an access token
                access_token = self.service.GetAccessToken(oauth_token)
                logging.info(access_token.__dict__)
                memcache_client = memcache.Client()
                session = {'access_token': access_token}
                memcache_client.set(self.settings['MEMCACHE_USER_STORE'] % user_id, session, time=120)
            except:
                logging.warn(traceback.format_exc())
            finally:
                return _CLOSE_PAGE

        #===========================================================================================
        # REQUEST TOKEN STEP. Provide the data scope(s) and the page we'll
        # be redirected back to after the user grants access on the approval page.
        #===========================================================================================
        req_token = self.service.GetOAuthToken(self.settings['SCOPES'],
                self.request.scheme + '://' + self.request.host +
                        self.request.path + '?userId=%s' % (user_id),
                self.settings['CONSUMER_KEY'],
                consumer_secret=self.settings['CONSUMER_SECRET'])
        logging.info(req_token.__dict__)
        #===========================================================================================
        # When using HMAC, persist the token secret in order to re-create an
        # OAuthToken object coming back from the approval page.
        #===========================================================================================
        gdata.gauth.AeSave(req_token, self.settings['MEMCACHE_USER_STORE'] % user.email())

        #===========================================================================================
        # Generate the URL to redirect the user to.  Add the hd paramter for a
        # better user experience.  Leaving it off will give the user the choice
        # of what account (Google vs. Google Apps) to login with.
        #===========================================================================================
        # APPROVAL STEP.  Redirect to user to Google's OAuth approval page.
        logging.info(req_token.generate_authorization_url())
        logging.info(req_token.generate_authorization_url().__dict__)
        logging.info(type(req_token.generate_authorization_url()))
        logging.info(unicode(req_token.generate_authorization_url()))
        logging.info(str(req_token.generate_authorization_url()))
        self.redirect(unicode(req_token.generate_authorization_url()))

    def logout(self):
        logging.info('logout')
        location = users.create_logout_url(self.request.scheme +
                    '://' + self.request.host +
                    '/service/%s/authCheck?a=1&b=2' % self.settings['SERVICE_NAME'])
        return '''
            <html><head><title></title>
            <script>
                function resize(){
                    window.parent.postMessage('[{"action": "setGwextWndTitle", "title": "Log in"}, {"action": "setGwextFrameHeight", "height":"500px"}, {"action":"setGwextFrameWidth","width":"600px"}]', 'https://wave.google.com/');
                }
                function redirect(location){
                    window.location = location;
                }
            </script>
            </head>
            <body onload="resize();redirect(\'''' + location + '''\');">
            </body>
            </html>
        '''
        self.redirect(users.create_logout_url(self.request.scheme +
                    '://' + self.request.host +
                    '/service/%s/authCheck?a=1&b=2' % self.settings['SERVICE_NAME']))

    def setOAuthToken(self):
        token_key = self.request.get('oauth_token')
        token_secret = self.request.get('oauth_token_secret')
        if not token_key or not token_secret:
            self.response.set_status(401)
            return
        token = gdata.auth.OAuthToken(key=token_key, secret=token_secret,
                                      scopes=self.settings['SCOPES'],
                                      oauth_input_params=self.service.GetOAuthInputParameters())
        self.service.current_token = token
        self.service.SetOAuthToken(token)
        return True

    def setOAuthTokenV2(self):
        token_key = self.request.get('oauth_token')
        token_secret = self.request.get('oauth_token_secret')
        if not token_key or not token_secret:
            self.response.set_status(401)
            return False
        from gdata.gauth import OAuthHmacToken
        self.service.auth_token = OAuthHmacToken(self.settings['CONSUMER_KEY'],
                                      self.settings['CONSUMER_SECRET'],
                                      token_key,
                                      token_secret,
                                      gdata.gauth.ACCESS_TOKEN)
        return True

class BloggerService(GoogleService):
    def __init__(self, **kwargs):
        from service.settings import BLOGGER_SETTINGS
        from gdata.blogger import service
        super(BloggerService, self).__init__(
                 settings=BLOGGER_SETTINGS,
                 service=service.BloggerService)
        self.actions['getBlogList'] = (self.getBlogList, 'text/javascript')
        self.actions['getPostList'] = (self.getPostList, 'text/javascript')
        self.actions['publishPost'] = (self.publishPost, 'text/javascript')

    def getBlogList(self):
        if not self.setOAuthToken(): return
        try:
            feed = self.service.GetBlogFeed()
        except RequestError, e:
            return self.getErrorResponse(e)
        blog_list = []
        for entry in feed.entry:
            title = entry.title.text
            blog_id = entry.GetSelfLink().href.split('/')[-1]
            blog_list.append({'id': blog_id,
                              'title': title})
        return simplejson.dumps({'blogs': blog_list})

    def getPostList(self):
        if not self.setOAuthToken(): return
        blog_id = self.request.get('blogId')
        if not blog_id: return
        try:
            feed = self.service.GetBlogPostFeed(blog_id)
        except RequestError, e:
            return self.getErrorResponse(e)
        post_list = []
        for entry in feed.entry:
            title = entry.title.text
            post_id = entry.GetSelfLink().href.split('/')[-1]
            post_list.append({'id': post_id,
                              'title': title})
        return simplejson.dumps({'posts': post_list})

    def publishPost(self):
        if not self.setOAuthToken(): return
        oauth_token = self.request.get('oauth_token')
        oauth_token_secret = self.request.get('oauth_token_secret')
        blog_id = self.request.get('blogId')
        post_id = self.request.get('postId')
        wave_id = self.request.get('waveId')
        wavelet_id = self.request.get('waveletId')
        blip_id = self.request.get('blipId')
        hash_str = self.request.get('hash')
        if not blog_id or not wave_id: return
        bloggerbot = self.getRobotInstance(wave_id)
        try:
            wavelet, wavelet_copy = self.getWaveletWithCopy(bloggerbot, wave_id, wavelet_id)
            blip = wavelet.blips.get(blip_id)
            blip_copy = wavelet_copy.blips.get(blip_id)
        except errors.RpcError, e:
            return self.getErrorResponse(e)
        from robotUtil.tag import findLink, generateLink, markTag
        try:
            start_pos, end_pos = findLink(self.request, blip, hash_str)
        except:
            return
        content, title = WaveletParser().parseBlip(blip, cut_tag='<!-- more -->')
        if not title:
            if self.locale == 'ru':
                title = 'Пост без заголовка'
            else:
                title = 'Post without a title'
        logging.info(content)
        if post_id:
            from gdata import service
            query = service.Query()
            query.feed = '/feeds/' + blog_id + '/posts/default/' + post_id
            try:
                entry = self.service.Get(query.ToUri())
            except RequestError, e:
                return self.getErrorResponse(e)
            entry.title = atom.Title('xhtml', title)
            entry.content = atom.Content(content_type='html', text=content)
            try:
                entry = self.service.UpdatePost(entry)
            except RequestError, e:
                return self.getErrorResponse(e)
        else:
            entry = gdata.GDataEntry()
            entry.title = atom.Title('xhtml', title)
            entry.content = atom.Content(content_type='html', text=content)
            try:
                entry = self.service.AddPost(entry, blog_id)
            except RequestError, e:
                return self.getErrorResponse(e)
        if self.locale == 'ru':
            blip_refs_str = u'(экспорт: Blogspot - %s)' % title
        else:
            blip_refs_str = '(export: Blogspot - %s)' % title
        blip_copy.range(start_pos, end_pos).replace(blip_refs_str)
        blipRefs = blip_copy.range(start_pos, start_pos + len(blip_refs_str))
        blipRefs.annotate('link/manual', generateLink(blip_copy, 0, hash_str, locale=self.locale) + '&service=%s&blogId=%s&postId=%s&token=%s&title=%s&link=%s' %
                          ('Blogger', blog_id, entry.GetSelfLink().href.split('/')[-1], urllib2.quote('oauth_token=%s&oauth_token_secret=%s' % (oauth_token, oauth_token_secret)), urllib2.quote(title.encode('utf-8')), urllib2.quote(entry.GetHtmlLink().href)))
        markTag(blipRefs)
        bloggerbot.submit(wavelet_copy)
        result = simplejson.dumps({
            'link': entry.GetHtmlLink().href,
            'title': entry.title.text,
            'postId': entry.GetSelfLink().href.split('/')[-1]
        })
        return result


class GDocsService(GoogleService):
    def __init__(self):
        from service.settings import GDOCS_SETTINGS
        from gdata.docs import client
        super(GDocsService, self).__init__(
                 settings=GDOCS_SETTINGS,
                 service=client.DocsClient)
        self.actions['getDocList'] = (self.getDocList, 'text/javascript')
        self.actions['postDoc'] = (self.postDoc, 'text/javascript')

    def getDocList(self):
        if not self.setOAuthTokenV2(): return
        wave_id = self.request.get('waveId')
        if not wave_id: return
        try:
            feed = self.service.GetDocList()
        except Exception, e:
            return self.getErrorResponse(e)
        doc_list = []
        for entry in feed.entry:
            title = entry.title.text
            doc_id = entry.resource_id.text
            if not re.match('document:', doc_id): continue
            doc_list.append({'id': urllib2.quote(doc_id),
                              'title': title})
        return simplejson.dumps({'gdocs': doc_list})

    def postDoc(self):
        '''
        Posting document to GDocs document.
        
        Get parameters:
        waveId - wave which contains the source of the publishing document;
        userId - gadget host
        gdocId (optional) - id of the publishing document. if not specified new document 
            will be created
        '''
        if not self.setOAuthTokenV2(): return
        doc_id = self.request.get('gdocId')
        wave_id = self.request.get('waveId')
        wavelet_id = self.request.get('waveletId')
        blip_id = self.request.get('blipId')
        hash_str = self.request.get('hash')
        oauth_token = self.request.get('oauth_token')
        oauth_token_secret = self.request.get('oauth_token_secret')
        logging.info(wave_id)
        if not wave_id:
            self.response.set_status(400)
            return
        if doc_id:
            try:
                entry = self.service.GetDoc(doc_id)
                for category in entry.category:
                    if category.label == 'trashed':
                        raise ServiceError, {'status': 404, 'body': 'Document has been moved to trash'}
            except Exception, e:
                return self.getErrorResponse(e)
        bloggerbot = self.getRobotInstance(wave_id)
        try:
            wavelet, wavelet_copy = self.getWaveletWithCopy(bloggerbot, wave_id, wavelet_id)
            blip = wavelet.blips.get(blip_id)
            blip_copy = wavelet_copy.blips.get(blip_id)
        except errors.RpcError, e:
            return self.getErrorResponse(e)
        from robotUtil.tag import findLink, generateLink, markTag
        try:
            start_pos, end_pos = findLink(self.request, blip, hash_str)
        except:
            return
        content, title = WaveletParser(parser=GDocsBlipParser).parseBlip(blip)
        if not title:
            if self.locale == 'ru':
                title = 'Документ без заголовка'
            else:
                title = 'Document without a title'
        content = '<h1>' + title.encode('UTF-8') + '</h1>' + content
        import StringIO
        file_handle = StringIO.StringIO(content)
        from gdata.data import MediaSource
        ms = MediaSource(file_handle=file_handle, content_type='text/html', content_length=len(content))
        if doc_id:
            try:
                entry.title.text = title
                entry = self.service.Update(entry, media_source=ms)
            except Exception, e:
                return self.getErrorResponse(e)
        else:
            try:
                entry = self.service.Upload(ms, title)
            except Exception, e:
                return self.getErrorResponse(e)
        if self.locale == 'ru':
            blip_refs_str = u'(экспорт: GDocs - %s)' % title
        else:
            blip_refs_str = '(export: GDocs - %s)' % title
        blip_copy.range(start_pos, end_pos).replace(blip_refs_str)
        blipRefs = blip_copy.range(start_pos, start_pos + len(blip_refs_str))
        blipRefs.annotate('link/manual', generateLink(blip_copy, 0, hash_str, locale=self.locale) + '&service=%s&gdocId=%s&token=%s&title=%s&link=%s' %
                          ('GDocs', entry.GetSelfLink().href.split('/')[-1], urllib2.quote('oauth_token=%s&oauth_token_secret=%s' % (oauth_token, oauth_token_secret)), urllib2.quote(title.encode('utf-8')), urllib2.quote(entry.GetHtmlLink().href)))
        markTag(blipRefs)
        bloggerbot.submit(wavelet_copy)
        result = simplejson.dumps({
            'link': entry.GetHtmlLink().href,
            'title': entry.title.text,
            'gdocId': entry.GetSelfLink().href.split('/')[-1]
        })
        return result


class VolnaSiteError(Exception):
    pass

class VolnaSiteService(ServiceHandler):
    def __init__(self):
        super(VolnaSiteService, self).__init__()
        from service.settings import VOLNA_SITE_SETTINGS
        self.settings = VOLNA_SITE_SETTINGS
        self.actions['authCheck'] = (self.makeAuth, 'text/html')
        self.actions['getMenuList'] = (self.getMenuList, 'text/javascript')
        self.actions['getDocList'] = (self.getDocList, 'text/javascript')
        self.actions['postDoc'] = (self.postDoc, 'text/javascript')

    def makeAuth(self):
        # making token request
        logging.info('makeAuth')
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
            return
        token = self.request.get('token')
        if token:
            session = {}
            session['access_token'] = token
            memcache_client = memcache.Client()
            memcache_client.set(self.settings['MEMCACHE_USER_STORE'] % user.email(), session, time=120)
            return _CLOSE_PAGE

        self.redirect(self.settings['AUTH_URL'] % urllib2.quote(self.request.uri))

    def getMenuList(self):
        # get menu list
        token = self.request.get('token')
        if not token:
            self.response.set_status(400)
            return
        try:
            data = self.requestRemoteData(self.settings['MENU_LIST_URL'] % token, VolnaSiteError)
        except VolnaSiteError, e:
            return self.getErrorResponse(e)
        return data

    def getDocList(self):
        # get document list
        token = self.request.get('token')
        menu_id = self.request.get('menuId')
        if not token: return
        try:
            data = self.requestRemoteData(self.settings['DOC_LIST_URL'] % (token, menu_id),
                                          VolnaSiteError)
        except VolnaSiteError, e:
            return self.getErrorResponse(e)
        return data

    def postDoc(self):
        # post document
        token = self.request.get('token')
        doc_id = self.request.get('docId')
        menu_id = self.request.get('menuId')
        wave_id = self.request.get('waveId')
        wavelet_id = self.request.get('waveletId')
        blip_id = self.request.get('blipId')
        hash_str = self.request.get('hash')
        logging.info(wave_id)
        if not token or not wave_id:
            self.response.set_status(400)
            return
        bloggerbot = self.getRobotInstance(wave_id)
        try:
            wavelet, wavelet_copy = self.getWaveletWithCopy(bloggerbot, wave_id, wavelet_id)
            blip = wavelet.blips.get(blip_id)
            blip_copy = wavelet_copy.blips.get(blip_id)
        except errors.RpcError, e:
            return self.getErrorResponse(e)
        from robotUtil.tag import findLink, generateLink, markTag
        try:
            start_pos, end_pos = findLink(self.request, blip, hash_str)
        except:
            return
        content, title = WaveletParser().parseBlip(blip)
        if not title:
            if self.locale == 'ru':
                title = 'Документ без заголовка'
            else:
                title = 'Document without a title'
        data = {
            'title': title,
            'content': content,
            'token': token,
            'menuId': menu_id
        }
        if doc_id: data['docId'] = doc_id
        try:
            data = simplejson.dumps(data)
            logging.info(data)
            data = self.requestRemoteData(self.settings['POST_URL'], VolnaSiteError, data)
        except VolnaSiteError, e:
            return self.getErrorResponse(e)
        logging.info(data)
        data = simplejson.loads(urllib2.unquote(data))
        if self.locale == 'ru':
            blip_refs_str = u'(экспорт: VolnaSite - %s)' % title
        else:
            blip_refs_str = '(export: VolnaSite - %s)' % title
        blip_copy.range(start_pos, end_pos).replace(blip_refs_str)
        blipRefs = blip_copy.range(start_pos, start_pos + len(blip_refs_str))
        
        blipRefs.annotate('link/manual', generateLink(blip_copy, 0, hash_str, self.locale) + '&service=%s&docId=%s&menuId=%s&token=%s&title=%s&link=%s' %
                          ('VolnaSite', data['docId'], menu_id, urllib2.quote(token), urllib2.quote(title.encode('utf-8')), urllib2.quote(data['docUrl'])))
        markTag(blipRefs)
        bloggerbot.submit(wavelet_copy)
        return simplejson.dumps({
            'title': title,
            'docId': data['docId'],
            'link': data['docUrl']
        })


class HtmlService(ServiceHandler):
    def __init__(self):
        super(HtmlService, self).__init__()
        from service.settings import HTML_SETTINGS
        self.settings = HTML_SETTINGS
        self.actions['authCheck'] = (self.makeAuth, 'text/html')
        self.actions['getDoc'] = (self.getDoc, 'text/plain')
        self.actions['postDoc'] = (self.postDoc, 'text/javascript')

    def makeAuth(self):
        wave_id = self.request.get('waveId')
        if not wave_id: return _CLOSE_PAGE
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
            return
        # making token request
        logging.info('makeAuth')
        html_token = models.HtmlService.all()
        html_token.filter('user_id =', user.email())
        html_token.filter('wave_id =', wave_id)
        html_entry = html_token.get()
        logging.info(html_entry)
        if not html_entry:
            html_entry = models.HtmlService(
                    token=sha1(('%f' % time.time()) + user.email()).hexdigest(),
                    user_id=user.email(),
                    wave_id=wave_id)
            html_entry.put()
        logging.info(html_entry)
        session = {}
        session['access_token'] = html_entry.token
        memcache_client = memcache.Client()
        memcache_client.set(self.settings['MEMCACHE_USER_STORE'] % user.email(), session, time=120)
        return _CLOSE_PAGE

    def getDoc(self):
        # get document list
        token = self.request.get('token')
        wave_id = self.request.get('waveId')
        user_id = self.request.get('userId')
        if not token or not wave_id or not user_id:
            self.response.set_status(400)
            return
        html_token = models.HtmlService.all()
        html_token.filter('user_id =', user_id)
        html_token.filter('wave_id =', wave_id)
        html_token.filter('token =', token)
        html_entry = html_token.get()
        if not html_entry:
            self.response.set_status(404)
            return
        return html_entry.doc.encode('UTF-8')

    def postDoc(self):
        # post document
        token = self.request.get('token')
        wave_id = self.request.get('waveId')
        user_id = self.request.get('userId')
        if not user_id:
            user_id = users.get_current_user().email()
        wavelet_id = self.request.get('waveletId')
        blip_id = self.request.get('blipId')
        hash_str = self.request.get('hash')
        if not token or not wave_id:
            self.response.set_status(400)
            return
        bloggerbot = self.getRobotInstance(wave_id)
        try:
            wavelet, wavelet_copy = self.getWaveletWithCopy(bloggerbot, wave_id, wavelet_id)
            blip = wavelet.blips.get(blip_id)
            blip_copy = wavelet_copy.blips.get(blip_id)
        except errors.RpcError, e:
            return self.getErrorResponse(e)
        from robotUtil.tag import findLink, generateLink, markTag
        try:
            start_pos, end_pos = findLink(self.request, blip, hash_str)
        except:
            return
        content, title = WaveletParser().parseBlip(blip)
        if not title:
            if self.locale == 'ru':
                title = 'Документ без заголовка'
            else:
                title = 'Document without a title'
        html_token = models.HtmlService.all()
        html_token.filter('user_id =', user_id)
        html_token.filter('wave_id =', wave_id)
        html_token.filter('token =', token)
        html_entry = html_token.get()
        if not html_entry:
            self.response.set_status(404)
            return
        html_entry.doc = content.decode('UTF-8')
        html_entry.put()
        logging.info(title)
        if self.locale == 'ru':
            blip_refs_str = u'(экспорт: Html - %s)' % title
        else:
            blip_refs_str = '(export: Html - %s)' % title
        blip_copy.range(start_pos, end_pos).replace(blip_refs_str)
        blipRefs = blip_copy.range(start_pos, start_pos + len(blip_refs_str))
        blipRefs.annotate('link/manual', generateLink(blip_copy, 0, hash_str, locale=self.locale) + '&service=%s&token=%s&title=%s&link=%s&userId=%s' %
                  ('Html',
                   token,
                   urllib2.quote(title.encode('utf-8')),
                   urllib2.quote(('https://' + self.request.host + '/service/Html/getDoc?token=%s&waveId=%s&userId=%s') % (token, urllib2.quote(wave_id), urllib2.quote(user_id))
                                 ),
                   urllib2.quote(user_id))
                  )
        markTag(blipRefs)
        bloggerbot.submit(wavelet_copy)
        return simplejson.dumps({
            'title': title,
            'link': 'https://' + self.request.host + '/service/Html/getDoc?' + self.request.query
        })

class LinkService(ServiceHandler):
    def __init__(self):
        super(LinkService, self).__init__()
        self.actions['updateLink'] = (self.updateLink, 'text/html')
        
    def updateLink(self):
        wave_id = self.request.get('waveId')
        wavelet_id = self.request.get('waveletId')
        blip_id = self.request.get('blipId')
        hash_str = self.request.get('hash')
        include = self.request.get('include')
        if include == '1':
            if self.locale == 'ru':
                text = u'(+включить)'
            else:
                text = '(+include)'
        else:
            if self.locale == 'ru':
                text = u'(-исключить)'
            else:
                text = '(-exclude)'
        bloggerbot = self.getRobotInstance(wave_id)
        try:
            wavelet = bloggerbot.fetch_wavelet(wave_id, wavelet_id)
        except errors.RpcError, e:
            return self.getErrorResponse(e)
        current_blip = wavelet.blips.get(blip_id)
        from robotUtil.tag import markTag, findIncludeLink, INCLUDE_LINK_STR_FORMAT
        try:
            start_pos, end_pos = findIncludeLink(current_blip, hash_str)
        except:
            return
        link = INCLUDE_LINK_STR_FORMAT % (SELF_DOMAIN, self.locale, wave_id, wavelet_id, blip_id, hash_str, include)
        current_blip.range(start_pos, end_pos).replace(text)
        blipRefs = current_blip.range(start_pos, start_pos+len(text))
        blipRefs.annotate('link/manual', link)
        markTag(blipRefs)
        bloggerbot.submit(wavelet)


def run():
    app = webapp.WSGIApplication([
        ('/service/Blogger/(.*)', BloggerService),
        ('/service/GDocs/(.*)', GDocsService),
        ('/service/VolnaSite/(.*)', VolnaSiteService),
        ('/service/Html/(.*)', HtmlService),
        ('/service/Link/(.*)', LinkService),
    ], debug=True)
    run_wsgi_app(app)


if __name__ == '__main__':
    run()
